package com.infomancers.workflow;

import com.infomancers.collections.Action;
import com.infomancers.collections.Predicate;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Copyright (c) 2007, Aviad Ben Dov
 * <p/>
 * All rights reserved.
 * <p/>
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 * <p/>
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * Neither the name of the Infomancers nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
public final class Transition {
    private Collection<Place> inputPlaces;
    private Collection<Place> outputPlaces;
    
    private Predicate<Token> predicate;
    private Action<Object> action;
    
    private FlowManager flowManager;
    
    public void setInputPlaces(Collection<Place> inputPlaces) {
        this.inputPlaces = inputPlaces;
    }
    
    public void setOutputPlaces(Collection<Place> outputPlaces) {
        this.outputPlaces = outputPlaces;
    }
    
    public Collection<Place> getInputPlaces() {
        return inputPlaces;
    }

    public Collection<Place> getOutputPlaces() {
        return outputPlaces;
    }
    
    public void setPredicate(Predicate<Token> predicates) {
        this.predicate = predicates;
    }
    
    public void setAction(Action<Object> actions) {
        this.action = actions;
    }
    
    public void setFlowManager(FlowManager flowManager) {
        this.flowManager = flowManager;
    }
    
    public boolean tryFire(Token token) {
        // Check if this token is right for the pre-conditions
        // of this transition.
        if (predicate != null && !predicate.isTrue(token)) {
            return false;
        }
        
        if (inputPlaces != null) {
            // Try to lock all inputs for removing the token,
            // and remove it if possible.
            synchronized (inputPlaces) {
                if (!lockTokenOnInput(token)) {
                    unlockTokenOnInput(token);
                    return false;
                }
                
                for (Place p : inputPlaces) {
                    p.removeToken(this, token);
                }
            }
        }
        
        // Run the internal firing mechanism
        internalFire(token);
        
        // Send the token to the next places.
        if (outputPlaces != null) {
            for (Place p : outputPlaces) {
                flowManager.send(token, p);
            }
        }
        
        return true;
    }

    private void internalFire(Token token) {
        if (action == null) {
            return;
        }
        
        try {
            action.accept(token.getData());
        } catch (Exception ex) {
            Logger.getLogger(Transition.class.getName()).log(Level.SEVERE, "Calling an action on token data", ex);
        }
    }
    
    private boolean lockTokenOnInput(Token token) {
        for (Place p : inputPlaces) {
            if (!p.lockToken(this, token)) {
                return false;
            }
        }
        
        return true;
    }
    
    private void unlockTokenOnInput(Token token) {
        for (Place p : inputPlaces) {
            p.unlockToken(this, token);
        }
    }
}
